package complet.app.tool.paramlearning;

import complet.db.Evaluator;
import complet.db.genetics.BufferedEvaluator;
import complet.helper.ConfigHelper;
import complet.helper.IOHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

public class genetics {

    public static void main(String[] args) {
        genetics app = new genetics();
        app.start();
    }

    /* Implementierung des genetischen Algorithmus */
    private final int populationsgroesse = 40;
    private final int anz_generationen = 350;
    private final boolean doLocNorm = true;
    private final float p_m = 0.01f; // Mutationswahrscheinlichkeit
    private final float p_c = 1f; // Crossover-Wahrscheinlichkeit
    private Random rnd = new Random();
    private BufferedEvaluator evl;
    private BufferedWriter geneticsLogFile = IOHelper.openWriteFile("Genetics.log");
    private BufferedWriter bufferLogFile = IOHelper.openAppendFile("Genetics.buffer");
    private BufferedReader bufferReadFile = IOHelper.openReadFile("Genetics.buffer");
    private HashMap<String, Integer> buffer = new HashMap<String, Integer>();
    private int[] fitness = new int[populationsgroesse + (populationsgroesse / 2)];
    private List<int[]> population = new ArrayList<int[]>();

    private void start() {

        // 1) Initialisierung
        ConfigHelper.DB_PATH = "E:\\paul\\Dokumente\\MyDocuments\\NetBeans Workspace\\Completion\\dbs\\db_ngrams_r5.db";
        ConfigHelper.IN_PATH = "E:\\paul\\Dokumente\\MyDocuments\\NetBeans Workspace\\Completion\\inputfiles\\wikipedia\\wiki.txt";
        ConfigHelper.BUFFER = true;
        evl = new BufferedEvaluator();
        if (doLocNorm) {
            evl.setSort(1);
        } else {
            evl.setSort(0);
        }
        evl.setAnz_stnce(1000);

        // Load buffer
        String stnce;
        String[] b;
        try {
            while ((stnce = bufferReadFile.readLine()) != null) {
                b = stnce.split("\t");
                buffer.put(b[0], Integer.parseInt(b[1]));
            }
        } catch (Exception e) {
            IOHelper.logError("(genetics) Failed to load buffer file: " + e.getMessage());
        }

        logGenetics("Start genetic algorithm. Population size: " + populationsgroesse + ". Max. Generations: " + anz_generationen, true);

//        int[] tmp = new int[4];
//        tmp[0] = 8;
//        tmp[1] = 51;
//        tmp[2] = 84;
//        tmp[3] = 64;
        //population.add(tmp);
        for (int i = 0; i < populationsgroesse; i++) {
            population.add(generiereChromosom());
            //population.add(tmp.clone());
        }

        // solange Abbruchbedingung nicht erfÃ¼llt
        int i = 1;
        while (i <= anz_generationen) {
            logGenetics("Generation " + i, true);
            // Population anzeigen
            zeigePopulation(i);

            // 2) Evaluation
            IOHelper.pln("Berechne Fittness");
            berechneFitness();
            //for (int j = 0; j < populationsgroesse; j++) {
            //    fitness[j]  = rnd.nextInt(100);
            //}

            // Zeige bestes Chromosom an
            zeigeBestesChromosomAn(i);

            // 3) Selektion
            IOHelper.pln("selektiere...");
            selektiere();

            // 4) Rekombination
            IOHelper.pln("rekombiniere eltern...");
            rekombiniere();

            // 5) Mutation
            IOHelper.pln("mutiere kinder...");
            mutiere();

            i++;

        }

    }

    private void zeigePopulation(int generation) {
        for (int i = 0; i < population.size(); i++) {
            // Chromosom als Zeichenkette darstellen
            String allele = "";
            int[] chromosom = population.get(i);
            for (int j = 0; j < chromosom.length; j++) {
                allele += ((j == 0) ? "" : ", ") + (chromosom[j] + 1);
            }
            logGenetics(generation + ". Generation " + (i + 1) + ". Chromosom : " + allele, false);
        }
    }

    private void zeigeBestesChromosomAn(int generation) {
        int beste_fitness = -1;
        int beste_fitness_index = -1;

        // Beste Fitness finden
        for (int i = 0; i < population.size(); i++) {
            int f = fitness[i];
            if (f > beste_fitness) {
                beste_fitness = f;
                beste_fitness_index = i;
            }
        }

        // Chromosom als Zeichenkette darstellen
        String bestes_chromosom = "";
        int[] chromosom = population.get(beste_fitness_index);
        for (int i = 0; i < chromosom.length; i++) {
            bestes_chromosom += ((i == 0) ? "" : ", ") + (chromosom[i]);
        }

        // neue Zeile in Auswertungstabelle schreiben
        logGenetics(generation + ". Generation bestes Chromosom " + (beste_fitness_index + 1) + "(" + bestes_chromosom + ") hat Fitness " + beste_fitness, true);
    }

    private void berechneFitness() {
        int[] chromosom;
        int[] fit_0;
        int[] fit_1;
        int[] fit_2;
        int[] fit_3;
        String key_b;

        for (int i = 0; i < population.size(); i++) {
            logGenetics("Fitness für Individuum " + (i + 1), false);
            chromosom = population.get(i);

            // Prebuffered?
            key_b = chromosom[0] + ":" + chromosom[1] + ":" + chromosom[2] + ":" + chromosom[3];
            if (buffer.containsKey(key_b)) {
                fitness[i] = buffer.get(key_b);
                logGenetics("Buffered fitness...", false);
            } else {
                evl.setP1(chromosom[0]);
                evl.setP2(chromosom[1]);
                evl.setP3(chromosom[2]);
                evl.setP4(chromosom[3]);

                // Evaluate
                evl.eval();

                // fittnes
                if (doLocNorm) {
                    fit_0 = evl.getLoc_0();
                    fit_1 = evl.getLoc_1();
                    fit_2 = evl.getLoc_2();
                    fit_3 = evl.getLoc_3();
                } else {
                    fit_0 = evl.getCnt_0();
                    fit_1 = evl.getCnt_1();
                    fit_2 = evl.getCnt_2();
                    fit_3 = evl.getCnt_3();
                }
                fitness[i] = ((fit_0[0] * 10 + fit_1[0] * 7 + fit_2[0] * 5 + fit_3[0] * 3) + (fit_0[1] * 7 + fit_1[1] * 4 + fit_2[1] * 2 + fit_3[1]));
                buffer.put(key_b, fitness[i]);
                try {
                    bufferLogFile.write(key_b + "\t" + fitness[i] + "\n");
                    bufferLogFile.flush();
                } catch (Exception e) {
                    IOHelper.logError("(genetics) Failed to write buffer file: " + e.getMessage());
                }

            }
        }

    }

    private int[] generiereChromosom() {
        // ein Chromosom enthälte die Allele p1 bis p4, welche die Gewichte der Kanten repräsentieren
        int[] chromosom = new int[4];

        for (int i = 0; i < 4; i++) {
            chromosom[i] = rnd.nextInt(101);
        }


        return chromosom;
    }

    private void rekombiniere() {
        // - immer zwei Chromosomen zufällig wählen
        // - daraus neue Chromosomen erstellen (rekombinieren), bis die population wieder voll ist

        int rnd1 = 0;
        int rnd2 = 0;
        int[] p1;
        int[] p2;

        while (population.size() < populationsgroesse) {
            rnd1 = rnd.nextInt(population.size());
            do {
                rnd2 = rnd.nextInt(population.size());
            } while (rnd1 == rnd2 && population.size() > 1);
            p1 = population.get(rnd1);
            p2 = population.get(rnd2);

            // Do crossover
            if (rnd.nextFloat() <= p_c) {
                // Austauschstelle zufÃ¤llig bestimmen
                int z = rnd.nextInt(p1.length);

                int[] c = new int[4];
                System.arraycopy(p1, 0, c, 0, z);
                System.arraycopy(p2, z, c, z, 4 - z);

                population.add(c);
            }

        }

    }

    private void mutiere() {
        for (int[] chromosom : population) {
            for (int i = 0; i < chromosom.length; i++) {
                // mutiere mit Wahrscheinlichkeit p_m
                if (rnd.nextFloat() <= p_m) {
                    // Addiere einen Wert von -5 bis 5
                    chromosom[i] = chromosom[i] + (rnd.nextInt(10) - 5);
                }
            }
        }
    }

    private void selektiere() {
        // Die bessere Hälfter der Individuen überleben

        List<int[]> neue_population = new ArrayList<int[]>();

        for (int i = 0; i < populationsgroesse / 2; i++) {
            int helper = -1;
            int helper_ind = -1;
            // Maximum bestimmen
            for (int j = 0; j < population.size(); j++) {
                if (fitness[j] > helper) {
                    helper = fitness[j];
                    helper_ind = j;
                }
            }
            neue_population.add(population.get(helper_ind).clone());
            population.remove(helper_ind);
        }

        population = neue_population;

    }

    private void logGenetics(String s, boolean l) {
        if (l) {
            IOHelper.log("(genetics) " + s);
        }
        try {
            geneticsLogFile.write(s + "\n");
            geneticsLogFile.flush();
        } catch (Exception e) {
            IOHelper.logError("(genetics) Failed to write genetic log: " + e.getMessage());
        }
    }
    /* CODE-FRIEDHOF */
    //    private void rekombiniere() {
//        // - immer zwei Chromosomen zufällig wählen
//        // - daraus neue Chromosomen erstellen (rekombinieren)
//
//        int rnd1 = 0;
//        int rnd2 = 0;
//        int[] p1;
//        int[] p2;
//
//        kinder.clear();
//        for (int i = 0; i < population.size() / 2; i++) {
//            rnd1 = rnd.nextInt(population.size());
//            do {
//                rnd2 = rnd.nextInt(population.size());
//            } while (rnd1 == rnd2 && population.size() > 1);
//            p1 = population.get(rnd1);
//            p2 = population.get(rnd2);
//
//            // Do crossover
//            if (rnd.nextFloat() <= p_c) {
//                // Austauschstelle zufÃ¤llig bestimmen
//                int z = rnd.nextInt(p1.length+1);
//
//                int[] c = new int[4];
//                System.arraycopy(p1, 0, c, 0, z);
//                System.arraycopy(p2, z, c, z, 4 - z);
//
//                kinder.add(c);
//            }
//
//        }
//
//    }
//
//    private void mutiere() {
//        for (int[] chromosom : kinder) {
//            for (int i = 0; i < chromosom.length; i++) {
//                // mutiere mit Wahrscheinlichkeit p_m
//                if (rnd.nextFloat() <= p_m) {
//                    // Addiere einen Wert von -5 bis 5
//                    chromosom[i] = chromosom[i] + (rnd.nextInt(3) - 1);
//                }
//            }
//        }
//    }
//
//    private void selektiere() {
//        // Die schlechtesten Individuen aussortieren, bis Populationsgröße steht
//        while (population.size() > populationsgroesse) {
//            int helper = Integer.MAX_VALUE;
//            int helper_ind = -1;
//            // Minimum bestimmen
//            for (int j = 0; j < population.size(); j++) {
//                if (fitness[j] < helper) {
//                    helper = fitness[j];
//                    helper_ind = j;
//                }
//            }
//            population.remove(helper_ind);
//        }
//    }
}
